#include "physics.h"
#include "..\common\def.h"
#include "..\common\util.h"
#include "..\engine\error.h"
#include "..\engine\engine.h"

#include "..\render\d3d_mesh.h"

#include "NxPhysics.h"
#include "NxStream.h"
#include "NxCooking.h"

class MemoryWriteBuffer : public NxStream
	{
	public:
								MemoryWriteBuffer();
	virtual						~MemoryWriteBuffer();
				void			clear();

	virtual		NxU8			readByte()								const	{ NX_ASSERT(0);	return 0;	}
	virtual		NxU16			readWord()								const	{ NX_ASSERT(0);	return 0;	}
	virtual		NxU32			readDword()								const	{ NX_ASSERT(0);	return 0;	}
	virtual		float			readFloat()								const	{ NX_ASSERT(0);	return 0.0f;}
	virtual		double			readDouble()							const	{ NX_ASSERT(0);	return 0.0;	}
	virtual		void			readBuffer(void* buffer, NxU32 size)	const	{ NX_ASSERT(0);				}

	virtual		NxStream&		storeByte(NxU8 b);
	virtual		NxStream&		storeWord(NxU16 w);
	virtual		NxStream&		storeDword(NxU32 d);
	virtual		NxStream&		storeFloat(NxReal f);
	virtual		NxStream&		storeDouble(NxF64 f);
	virtual		NxStream&		storeBuffer(const void* buffer, NxU32 size);

				NxU32			currentSize;
				NxU32			maxSize;
				NxU8*			data;
	};

class MemoryReadBuffer : public NxStream
	{
	public:
								MemoryReadBuffer(const NxU8* data);
	virtual						~MemoryReadBuffer();

	virtual		NxU8			readByte()								const;
	virtual		NxU16			readWord()								const;
	virtual		NxU32			readDword()								const;
	virtual		float			readFloat()								const;
	virtual		double			readDouble()							const;
	virtual		void			readBuffer(void* buffer, NxU32 size)	const;

	virtual		NxStream&		storeByte(NxU8 b)							{ NX_ASSERT(0);	return *this;	}
	virtual		NxStream&		storeWord(NxU16 w)							{ NX_ASSERT(0);	return *this;	}
	virtual		NxStream&		storeDword(NxU32 d)							{ NX_ASSERT(0);	return *this;	}
	virtual		NxStream&		storeFloat(NxReal f)						{ NX_ASSERT(0);	return *this;	}
	virtual		NxStream&		storeDouble(NxF64 f)						{ NX_ASSERT(0);	return *this;	}
	virtual		NxStream&		storeBuffer(const void* buffer, NxU32 size)	{ NX_ASSERT(0);	return *this;	}

	mutable		const NxU8*		buffer;
	};


MemoryWriteBuffer::MemoryWriteBuffer() : currentSize(0), maxSize(0), data(NULL)
	{
	}

MemoryWriteBuffer::~MemoryWriteBuffer()
	{
	NX_DELETE_ARRAY(data);
	}

void MemoryWriteBuffer::clear()
	{
	currentSize = 0;
	}

NxStream& MemoryWriteBuffer::storeByte(NxU8 b)
	{
	storeBuffer(&b, sizeof(NxU8));
	return *this;
	}
NxStream& MemoryWriteBuffer::storeWord(NxU16 w)
	{
	storeBuffer(&w, sizeof(NxU16));
	return *this;
	}
NxStream& MemoryWriteBuffer::storeDword(NxU32 d)
	{
	storeBuffer(&d, sizeof(NxU32));
	return *this;
	}
NxStream& MemoryWriteBuffer::storeFloat(NxReal f)
	{
	storeBuffer(&f, sizeof(NxReal));
	return *this;
	}
NxStream& MemoryWriteBuffer::storeDouble(NxF64 f)
	{
	storeBuffer(&f, sizeof(NxF64));
	return *this;
	}
NxStream& MemoryWriteBuffer::storeBuffer(const void* buffer, NxU32 size)
	{
	NxU32 expectedSize = currentSize + size;
	if(expectedSize > maxSize)
		{
		maxSize = expectedSize + 4096;

		NxU8* newData = new NxU8[maxSize];
		NX_ASSERT(newData!=NULL);

		if(data)
			{
			memcpy(newData, data, currentSize);
			delete[] data;
			}
		data = newData;
		}
	memcpy(data+currentSize, buffer, size);
	currentSize += size;
	return *this;
	}


MemoryReadBuffer::MemoryReadBuffer(const NxU8* data) : buffer(data)
	{
	}

MemoryReadBuffer::~MemoryReadBuffer()
	{
	// We don't own the data => no delete
	}

NxU8 MemoryReadBuffer::readByte() const
	{
	NxU8 b;
	memcpy(&b, buffer, sizeof(NxU8));
	buffer += sizeof(NxU8);
	return b;
	}

NxU16 MemoryReadBuffer::readWord() const
	{
	NxU16 w;
	memcpy(&w, buffer, sizeof(NxU16));
	buffer += sizeof(NxU16);
	return w;
	}

NxU32 MemoryReadBuffer::readDword() const
	{
	NxU32 d;
	memcpy(&d, buffer, sizeof(NxU32));
	buffer += sizeof(NxU32);
	return d;
	}

float MemoryReadBuffer::readFloat() const
	{
	float f;
	memcpy(&f, buffer, sizeof(float));
	buffer += sizeof(float);
	return f;
	}

double MemoryReadBuffer::readDouble() const
	{
	double f;
	memcpy(&f, buffer, sizeof(double));
	buffer += sizeof(double);
	return f;
	}

void MemoryReadBuffer::readBuffer(void* dest, NxU32 size) const
	{
	memcpy(dest, buffer, size);
	buffer += size;
	}


NxConvexShapeDesc CPhysics::GetModelMesh(D3DModel* pModel)
{
	/*if(ISNULL(mp_CookingInterface))
	{
		mp_CookingInterface = NxGetCookingLib(NX_PHYSICS_SDK_VERSION);
		if(ISNULL(mp_CookingInterface))
			throw EE_EXCEPTION("Failed to get cooking library.");

		if(!mp_CookingInterface->NxInitCooking())
			throw EE_EXCEPTION("Failed to initalize cooking library.");
	}*/
	
	if(ISNULL(pModel))
		throw EE_EXCEPTION("Bad parameter.");

	if(pModel->getLODCount() == 0)
		throw EE_EXCEPTION("Bad data.");

	D3DModelLOD* pBaseLOD = pModel->getLOD(0);
	if(ISNULL(pBaseLOD))
		throw EE_EXCEPTION("Bad LOD data.");

	unsigned int totalVertexCount = 0;

	for(unsigned int i = 0; i < pBaseLOD->getMeshCount(); i++)
	{
		D3DMesh* pMesh = pBaseLOD->getMesh(i);
		totalVertexCount += pMesh->getVertexCount();
	}

	NxVec3* pVertices = EE_NEW NxVec3[totalVertexCount];
	NxU32* pIndicies = EE_NEW NxU32[totalVertexCount];

	for(unsigned int i = 0, baseIdx = 0; i < pBaseLOD->getMeshCount(); i++)
	{
		D3DMesh* pMesh = pBaseLOD->getMesh(i);
		VertexComplex* pMeshVertices = (VertexComplex*)pMesh->getVertices();
		unsigned short* pMeshIndicies = pMesh->getIndicies();

		for(unsigned int j = 0; j < pMesh->getVertexCount(); j++)
		{
			float* pPos = pMeshVertices[j].position;
			pVertices[baseIdx].x = pPos[0];
			pVertices[baseIdx].y = pPos[1];
			pVertices[baseIdx].z = pPos[2];

			pIndicies[baseIdx] = (NxU32)pMeshIndicies[j];
			baseIdx ++;
		}
	}

	NxConvexMeshDesc meshShape;
	meshShape.setToDefault();
	meshShape.numVertices = totalVertexCount;
	meshShape.numTriangles = totalVertexCount / 3;
	meshShape.pointStrideBytes = sizeof(NxVec3);
	meshShape.triangleStrideBytes = sizeof(NxU32) * 3;
	meshShape.points = pVertices;
	meshShape.triangles = pIndicies;
	meshShape.flags = 0;

	if(!NxInitCooking())
		throw EE_EXCEPTION("Failed to initalize cooking library.");

	MemoryWriteBuffer writeBuffer;
	if(!NxCookConvexMesh(meshShape, writeBuffer))
		throw EE_EXCEPTION("Failed to cook triangle mesh.");

	NxConvexShapeDesc triangleMeshDesc;
	triangleMeshDesc.density = 10.0f;
	triangleMeshDesc.meshData = mp_SDK->createConvexMesh(MemoryReadBuffer(writeBuffer.data));

	EE_SAFEDELETEARRAY(pVertices);
	EE_SAFEDELETEARRAY(pIndicies);

	return triangleMeshDesc;
}